package org.netbeans.modules.xml.schema.hyperlink;

import java.util.ArrayList;
import java.util.List;
import javax.swing.text.AbstractDocument;
import javax.swing.text.Document;
import javax.xml.XMLConstants;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.api.xml.lexer.XMLTokenId;
import org.netbeans.editor.BaseDocument;
import org.netbeans.modules.editor.NbEditorUtilities;
import org.openide.filesystems.FileObject;

/**
 *
 * @author Rohan Ranade
 */
public final class Utilities {

    private Utilities() {
    }

    public static class DocRootAttribute {

        private final String name;
        private final String value;

        DocRootAttribute(String name, String value) {
            this.name = name;
            this.value = value;
        }

        public String getName() {
            return name;
        }

        public String getValue() {
            return value;
        }

        @Override
        public String toString() {
            return name + "=" + value;
        }
    }

    public static final class DocRoot {
        //name of the root along with prefix, e.g. po:purchaseOrder

        private final String name;
        private final List<DocRootAttribute> attributes;

        DocRoot(String name, List<DocRootAttribute> attributes) {
            this.name = name;
            this.attributes = new ArrayList<DocRootAttribute>(attributes);
        }

        public String getName() {
            return name;
        }

        public String getPrefix() {
            return getPrefixFromTag(name);
        }

        public List<DocRootAttribute> getAttributes() {
            return attributes;
        }

        public boolean declaresNamespace() {
            for (DocRootAttribute attr : getAttributes()) {
                if (attr.getName().startsWith(XMLConstants.XMLNS_ATTRIBUTE)) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * Returns the prefix from the element's tag.
     */
    public static String getPrefixFromTag(String tagName) {
        if (tagName == null) {
            return null;
        }
        return (tagName.indexOf(":") == -1) ? null : tagName.substring(0, tagName.indexOf(":"));
    }

    /**
     * Returns the local name from the element's tag.
     */
    public static String getLocalNameFromTag(String tagName) {
        if (tagName == null) {
            return null;
        }
        return (tagName.indexOf(":") == -1) ? tagName : tagName.substring(tagName.indexOf(":") + 1, tagName.length());
    }

    /**
     * Checks to see if this document declares any DOCTYPE or not?
     * If exists, it must appear before the first xml tag.
     * @return true if found, else false.
     */
    public static boolean isDTDBasedDocument(Document document) {
        ((AbstractDocument) document).readLock();
        try {
            TokenHierarchy th = TokenHierarchy.get(document);
            TokenSequence ts = th.tokenSequence();
            while (ts.moveNext()) {
                Token token = ts.token();
                //if an xml tag is found, we have come too far.
                if (token.id() == XMLTokenId.TAG) {
                    return false;
                }
                if (token.id() == XMLTokenId.DECLARATION) {
                    return true;
                }
            }
        } finally {
            ((AbstractDocument) document).readUnlock();
        }
        return false;
    }

    /**
     * Can't provide completion for XML documents based on DTD
     * and for those who do not declare namespaces in root tag.
     */
    public static boolean canProvideHyperlinks(BaseDocument doc) {
        FileObject file = NbEditorUtilities.getFileObject(doc);
        if (file == null) {
            return false;
        }
        //for .xml documents
        if ("xml".equalsIgnoreCase(file.getExt())) { //NOI18N
            //if DTD based, no completion
            if (isDTDBasedDocument(doc)) {
                return false;
            }
            //if docroot doesn't declare ns, no completion
            DocRoot root = getDocRoot(doc);
            if (root != null && !root.declaresNamespace()) {
                return false;
            }
        }

        return true;
    }

    /**
     * Finds the root element of the xml document. It also populates the
     * attributes for the root element.
     *
     * See DocRoot.
     */
    public static DocRoot getDocRoot(Document document) {
        TokenHierarchy th = TokenHierarchy.get(document);
        TokenSequence ts = th.tokenSequence();
        List<DocRootAttribute> attributes = new ArrayList<DocRootAttribute>();
        String name = null;
        while (ts.moveNext()) {
            Token nextToken = ts.token();
            if (nextToken.id() == XMLTokenId.TAG) {
                String tagName = nextToken.text().toString();
                if (name == null && tagName.startsWith("<")) {
                    name = tagName.substring(1, tagName.length());
                }
                String lastAttrName = null;
                while (ts.moveNext()) {
                    Token t = ts.token();
                    if (t.id() == XMLTokenId.TAG && t.text().toString().equals(">")) {
                        break;
                    }
                    if (t.id() == XMLTokenId.ARGUMENT) {
                        lastAttrName = t.text().toString();
                    }
                    if (t.id() == XMLTokenId.VALUE && lastAttrName != null) {
                        String value = t.text().toString();
                        if (value == null || value.length() == 1) {
                            value = null;
                        } else if (value.startsWith("'") || value.startsWith("\"")) {
                            value = value.substring(1, value.length() - 1);
                        }
                        attributes.add(new DocRootAttribute(lastAttrName, value));
                        lastAttrName = null;
                    }
                } //while loop

                //first start tag with a valid name is the root
                if (name != null) {
                    break;
                }
            }
        } //while loop

        return new DocRoot(name, attributes);
    }
}
